package com.hyw.encryption.utils;

import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.springframework.util.ReflectionUtils;

public final class RSACoder {

  public static final String KEY_ALGORITHM = "RSA";
  public static final String CIPHER_KEY_ALGORITHM = "RSA/ECB/PKCS1Padding";
  public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

  private static final String PUBLIC_KEY = "RSAPublicKey";
  private static final String PRIVATE_KEY = "RSAPrivateKey";

  private static final int MAX_ENCRYPT_BLOCK = 245;
  private static final int MAX_DECRYPT_BLOCK = 256;

  // 一个用来验证公钥有效性的一个采样字节序
  private static final byte[] Sample_Bytes = KEY_ALGORITHM.getBytes();

  /**
   * 添加一个方法用于验证给的公钥是不是有效的公钥
   * 
   * @param publicKey
   * @return
   */
  public static boolean isValidPublicKey(String publicKey) {
    boolean isValid = true;
    try {
      encryptByPublicKey(Sample_Bytes, publicKey);
    } catch (Exception ex) {
      isValid = false;
    }
    return isValid;
  }

  /**
   * 用私钥对信息生成数字签名
   * 
   * @param data
   *          加密数据
   * @param privateKey
   *          私钥
   * @return
   * @throws Exception
   */
  public static String sign(byte[] data, String privateKey) {
    String ret = null;

    // 解密由base64编码的私钥
    byte[] keyBytes = Base64.getDecoder().decode(privateKey);

    // 构造PKCS8EncodedKeySpec对象
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);

    // KEY_ALGORITHM 指定的加密算法
    try {
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      // 取私钥匙对象
      PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
      // 用私钥对信息生成数字签名
      Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
      signature.initSign(priKey);
      signature.update(data);
      ret = Base64.getEncoder().encodeToString(signature.sign());
    } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | InvalidKeySpecException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }
    return ret;
  }

  /**
   * 校验数字签名
   * 
   * @param data
   *          加密数据
   * @param publicKey
   *          公钥
   * @param sign
   *          数字签名
   * @return 校验成功返回true 失败返回false
   * @throws Exception
   */
  public static boolean verify(byte[] data, String publicKey, String sign) {
    boolean valid = false;

    // 解密由base64编码的公钥
    byte[] keyBytes = Base64.getDecoder().decode(publicKey);

    // 构造X509EncodedKeySpec对象
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);

    // KEY_ALGORITHM 指定的加密算法
    try {
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      // 取公钥匙对象
      PublicKey pubKey = keyFactory.generatePublic(keySpec);
      Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
      signature.initVerify(pubKey);
      signature.update(data);
      // 验证签名是否正常
      valid = signature.verify(Base64.getEncoder().encode(sign.getBytes()));
    } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | InvalidKeySpecException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }

    return valid;

  }

  /**
   * 解密<br>
   * 用私钥解密
   * 
   * @param data
   * @param key
   * @return
   * @throws Exception
   */
  public static byte[] decryptByPrivateKey(byte[] data, String key) {
    byte[] ret = null;

    // 对密钥解密
    byte[] keyBytes = Base64.getDecoder().decode(key);
    // 取得私钥
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
    try {
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
      // 对数据解密
      Cipher cipher = Cipher.getInstance(CIPHER_KEY_ALGORITHM);
      cipher.init(Cipher.DECRYPT_MODE, privateKey);

      int inputLen = data.length;
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int offSet = 0;
      byte[] cache;
      int i = 0;
      // 对数据分段解密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
          cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
        } else {
          cache = cipher.doFinal(data, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * MAX_DECRYPT_BLOCK;
      }
      ret = out.toByteArray();
    } catch (NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException
        | InvalidKeySpecException | NoSuchPaddingException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }
    return ret;
  }

  /**
   * 解密<br>
   * 用公钥解密
   * 
   * @param data
   * @param key
   * @return
   * @throws Exception
   */
  public static byte[] decryptByPublicKey(byte[] data, String key) {
    byte[] ret = null;

    // 对密钥解密
    byte[] keyBytes = Base64.getDecoder().decode(key);

    // 取得公钥
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
    try {
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      Key publicKey = keyFactory.generatePublic(x509KeySpec);
      // 对数据解密
      Cipher cipher = Cipher.getInstance(CIPHER_KEY_ALGORITHM);
      cipher.init(Cipher.DECRYPT_MODE, publicKey);

      int inputLen = data.length;
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int offSet = 0;
      byte[] cache;
      int i = 0;
      // 对数据分段解密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
          cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
        } else {
          cache = cipher.doFinal(data, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * MAX_DECRYPT_BLOCK;
      }
      ret = out.toByteArray();
    } catch (NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException
        | InvalidKeySpecException | NoSuchPaddingException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }
    return ret;
  }

  /**
   * 加密<br>
   * 用公钥加密
   * 
   * @param data
   * @param key
   * @return
   * @throws Exception
   */
  public static byte[] encryptByPublicKey(byte[] data, String key) {
    byte[] ret = null;
    // 对公钥解密
    byte[] keyBytes = Base64.getDecoder().decode(key);
    // 取得公钥
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
    try {
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      Key publicKey = keyFactory.generatePublic(x509KeySpec);

      // 对数据加密
      Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      int inputLen = data.length;
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int offSet = 0;
      byte[] cache;
      int i = 0;
      // 对数据分段加密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
          cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
        } else {
          cache = cipher.doFinal(data, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * MAX_ENCRYPT_BLOCK;
      }
      ret = out.toByteArray();
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidKeySpecException
        | IllegalBlockSizeException | BadPaddingException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }
    return ret;
  }

  /**
   * 加密<br>
   * 用私钥加密
   * 
   * @param data
   * @param key
   * @return
   * @throws Exception
   */
  public static byte[] encryptByPrivateKey(byte[] data, String key) {
    byte[] ret = null;

    // 对密钥解密
    byte[] keyBytes = Base64.getDecoder().decode(key);

    // 取得私钥
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);

    try {
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
      // 对数据加密
      Cipher cipher = Cipher.getInstance(CIPHER_KEY_ALGORITHM);
      cipher.init(Cipher.ENCRYPT_MODE, privateKey);

      int inputLen = data.length;
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int offSet = 0;
      byte[] cache;
      int i = 0;
      // 对数据分段加密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
          cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
        } else {
          cache = cipher.doFinal(data, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * MAX_ENCRYPT_BLOCK;
      }
      ret = out.toByteArray();
    } catch (NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException
        | NoSuchPaddingException | InvalidKeySpecException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }
    return ret;
  }

  /**
   * 取得私钥
   * 
   * @param keyMap
   * @return
   * @throws Exception
   */
  public static String getPrivateKey(Map<String, Object> keyMap) {
    Key key = (Key) keyMap.get(PRIVATE_KEY);
    return Base64.getEncoder().encodeToString(key.getEncoded());
  }

  /**
   * 取得公钥
   * 
   * @param keyMap
   * @return
   * @throws Exception
   */
  public static String getPublicKey(Map<String, Object> keyMap) {
    Key key = (Key) keyMap.get(PUBLIC_KEY);
    return Base64.getEncoder().encodeToString(key.getEncoded());
  }

  /**
   * 初始化密钥
   * 
   * @return
   * @throws Exception
   */
  public static Map<String, Object> initKey() {
    Map<String, Object> keyMap = new HashMap<>(2);
    try {
      KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
      keyPairGen.initialize(2048);
      KeyPair keyPair = keyPairGen.generateKeyPair();
      // 公钥
      RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
      // 私钥
      RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
      keyMap.put(PUBLIC_KEY, publicKey);
      keyMap.put(PRIVATE_KEY, privateKey);
    } catch (NoSuchAlgorithmException e) {
      ReflectionUtils.rethrowRuntimeException(e);
    }
    return keyMap;
  }
}
